package com.smartlazy.sequence.mysql.core.mysql;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.toolkit.IdWorker;
import com.smartlazy.sequence.common.biz.constant.CommonConst;
import com.smartlazy.sequence.mysql.core.entity.SysSequence;
import com.smartlazy.sequence.mysql.core.mapper.SysSequenceMapper;
import com.smartlazy.sequence.core.range.SeqRange;
import com.smartlazy.sequence.core.range.SeqRangeMgr;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;

/**
 * Mysql区间管理器
 * @author xuan on 2018/5/8.
 * https://gitee.com/xuan698400/xsequence
 */
@Data
public class MysqlSeqRangeMgr implements SeqRangeMgr {

    private static final long DELTA = 100000000L;

    private SysSequenceMapper sysSequenceMapper;

    /**
     * 前缀防止key重复
     */
    private final static String KEY_PREFIX = "x_sequence_";

    /**
     * 区间步长
     */
    private int step = 1000;

    /**
     * 获取区间失败重试次数
     */
    private int  retryTimes = 100;

    /**
     * 区间起始位置，真实从stepStart+1开始
     */
    private long stepStart = 0;

    /**
     * 获取指定区间名的下一个区间
     *
     * @param name 区间名
     * @return 返回区间
     */
    @Override
    public SeqRange nextRange(String name) throws Exception{
        if (StringUtils.isEmpty(name)) {
            throw new SecurityException("[MysqlSeqRangeMgr-nextRange] name is empty.");
        }
        Long oldValue;
        Long newValue;
        for (int i = 0; i < getRetryTimes(); i++) {
            oldValue = selectRange( name, getStepStart());

            if (null == oldValue) {
                //区间不存在，重试
                continue;
            }

            newValue = oldValue + getStep();

            if (updateRange(newValue, oldValue, name)) {
                return new SeqRange(oldValue + 1, newValue, name);
            }
        }
        //else 失败重试
        throw new Exception("Retried too many times, retryTimes = " + getRetryTimes());
    }

    private boolean updateRange(Long newValue, Long oldValue, String name) {
        SysSequence entity = new SysSequence();
        entity.setSeqValue(newValue);
        EntityWrapper<SysSequence> entityWrapper = new EntityWrapper<SysSequence>();
        entityWrapper.where("seq_name={0}", name)
                .and("seq_value={0}", oldValue);
        Integer ret = sysSequenceMapper.update(entity, entityWrapper);
        return ret != null && ret > 0 ;
    }

    private Long selectRange(String name, long stepStart) throws Exception {
        long oldValue;
        SysSequence input = new SysSequence();
        input.setSeqName(name);
        SysSequence one = sysSequenceMapper.selectOne(input);
        if (one == null) {
            // 没有此类型数据，需要初始化
            SysSequence entity = new SysSequence();
            entity.setId(IdWorker.getId());
            entity.setSeqName(name);
            entity.setSeqValue(stepStart);
            entity.setDeleteFlag(CommonConst.DELETE_FLAG_NOT_DELETE);
            entity.setAvailableFlag(CommonConst.AVAILABLE_FLAG_AVAILABLE);
            entity.setOrderNum(0);
            entity.setVersion(0);
            sysSequenceMapper.insert(entity);
            return null;
        }
        oldValue = one.getSeqValue();
        if (oldValue < 0) {
            String msg =
                    "Sequence value cannot be less than zero, value = " + oldValue + ", please check table sequence sys_sequence";
            throw new Exception(msg);
        }

        if (oldValue > Long.MAX_VALUE - DELTA) {
            String msg =
                    "Sequence value overflow, value = " + oldValue + ", please check table sequence sys_sequence";
            throw new Exception(msg);
        }

        return oldValue;
    }

    private String getRealKey(String name) {
        return KEY_PREFIX + name;
    }
 }
